#!/usr/bin/env python3
import json
import os
import shutil
from pathlib import Path
import time
import paramiko
import logging
from stat import S_ISDIR
from paramiko import SFTPClient
#import pdb

# 配置日志
logging.basicConfig(
    level=logging.INFO,  # 设置默认日志级别为 INFO
    format='%(asctime)s|%(levelname)-8s|%(lineno)-4u|%(message)s',  # 日志格式
    handlers=[
        logging.FileHandler("csms_upload.log"),  # 将日志写入文件
        logging.StreamHandler()  # 同时输出到控制台（可选）
    ]
)

def load_config(config_file):
    """加载配置文件"""
    try:
        with open(config_file, 'r') as f:
            config = json.load(f)
        return config
    except Exception as e:
        logging.error(f"load config file erro, file:{config_file}, errstr:{e}")
        exit(1)

def connect_sftp(config):
    while True:
        try:
            transport = paramiko.Transport((config["sftp_server"]["host"], config["sftp_server"]["port"]))
            transport.connect(username=config["sftp_server"]["username"], password=config["sftp_server"]["password"])
            sftp = paramiko.SFTPClient.from_transport(transport)
            return sftp, transport
        except Exception as e:
            logging.error(f"connect to server fail, errstr:{e}")
            logging.info(f"reconnect to server again after 10 seconds...")
            time.sleep(10)  # 等待 5 秒后重试

def is_sftp_connected(sftp):
    """检查 SFTP 连接是否正常"""
    try:
        # 尝试执行一个简单的操作来验证连接是否正常
        sftp.listdir('.')
        return True
    except Exception:
        return False

def make_remote_path(sftp: SFTPClient, remote_path: str):
    """ 确保远程路径存在，如不存在则逐级创建。 """
    # 如果路径以 '~/开头'，则移除 '~/'
    if remote_path.startswith("~/"):
        remote_path = remote_path[2:]  # 移除 '~/'

    # 如果路径为空，则直接返回
    if not remote_path:
        return

    # 规范化路径，避免多余的斜杠等问题, 只需确保没有多余的斜杠
    remote_part_paths = [part for part in remote_path.split("/") if part]
    total_part = ""
    for my_part in remote_part_paths:
        if not my_part:
            continue
        if not total_part:
            total_part = my_part 
        else:
            total_part = f"{total_part}/{my_part}"
        
        try:
            sftp.listdir(total_part)  # 尝试访问目录
        except IOError as e:
            # 只有当错误明确表示目录不存在时才尝试创建
            if "No such file" not in str(e) and "does not exist" not in str(e):
                logging.warning(f"remote path access error, remote_path:{total_part}, errstr:{e}")
                continue
            logging.info(f"remote path not found, create it, remote_path:{total_part}")
            try:
                sftp.mkdir(total_part)
            except Exception as mkdir_err:
                logging.error(f"remote path create error, path:{total_part}, errstr:{mkdir_err}")
                raise

def upload_files(sftp, local_dir, remote_dir, backup_dir, filenames, upload_backup_switch):
    """上传文件到远程目录中的文件"""
    # 创建远程目录
    make_remote_path(sftp, remote_dir)
    # 修正家目录
    remote_home_dir = sftp.normalize('.')
    if remote_dir.startswith("~/"):
        remote_dir = os.path.join(remote_home_dir, remote_dir[2:])

    # 上传文件
    for filename in filenames:
        local_path = os.path.join(local_dir, filename)
        remote_path = os.path.join(remote_dir, filename)
        backup_path = os.path.join(backup_dir, filename)
        tmp_remote_path = remote_path + ".tmp"
        try:
            logging.info(f"upload file: from:{local_path}, to:{remote_path}")
            sftp.put(local_path, tmp_remote_path)
            sftp.rename(tmp_remote_path, remote_path)
            if upload_backup_switch == 1:
                shutil.move(local_path, backup_path)
            else:
                os.remove(local_path)
        except Exception as e:
            logging.error(f"upload file error, errstr:{e}")

def scan_upload_files(sftp, config):
    """上传文件到远程目录"""
    upload_backup_switch =  config.get("upload_backup_switch", 0);
    while True:
        for task in config['upload_tasks']:
            local_dir = task['local_dir']
            remote_dir = task['remote_dir']
            backup_dir = task['backup_dir']

            # 检查本地路径是否存在
            if not os.path.isdir(local_dir):
                os.makedirs(local_dir, exist_ok=True)
                logging.info(f"local path not exist, create it: {local_dir}")
            if not os.path.isdir(backup_dir):
                os.makedirs(backup_dir, exist_ok=True)
                logging.info(f"backup path not exist, create it: {backup_dir}")

            # 扫描本地目录下的所有非.tmp 文件
            try:
                upload_file_list = [
                    f for f in os.listdir(local_dir)
                    if os.path.isfile(os.path.join(local_dir, f)) and not f.endswith('.tmp')
                ]
            except Exception as e:
                logging.error(f"scan local path error, local_dir:{local_dir}, errstr:{e}")
                time.sleep(5)
                continue
            
            # 上传文件
            if upload_file_list:
                upload_files(sftp, local_dir, remote_dir, backup_dir, upload_file_list, upload_backup_switch)
        # 每隔 10 秒扫描一次
        time.sleep(1)  

def main():
    logging.info("mainapp start...")
    config = load_config("csms_upload.json")
    server_ip = config["sftp_server"]["host"]
    server_port = config["sftp_server"]["port"]
    # 初始化 SFTP 连接
    sftp, transport = connect_sftp(config)
    logging.info("connect to server succ, server is {server_ip}:{server_port}")
    try:
        while True:
            # 检查连接状态，如果断开则重新连接
            if not is_sftp_connected(sftp):
                logging.warning("SFTP connection lost. Reconnecting...")
                logging.info("connect to server lost, server is {server_ip}:{server_port}")
                try:
                    transport.close()
                except Exception:
                    pass  # 忽略关闭连接时的错误
                sftp, transport = connect_sftp(config)
                logging.info("connect to server succ, server is {server_ip}:{server_port}")

            # 执行扫描任务
            scan_upload_files(sftp, config)
    except KeyboardInterrupt:
        logging.info("program terminated by user.")
    finally:
        # 关闭连接
        try:
            sftp.close()
            transport.close()
            logging.info("SFTP connection closed.")
        except Exception as e:
            logging.error(f"error closing SFTP connection: {e}")
        logging.info("mainapp end!!!")

if __name__ == "__main__":
    main()
